package com.wilmer.pay.service.impl;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.RequestMethodEnum;
import com.ijpay.core.kit.AesUtil;
import com.ijpay.core.kit.HttpKit;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.core.utils.DateTimeZoneUtil;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.enums.WxDomainEnum;
import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
import com.ijpay.wxpay.enums.v3.OtherApiEnum;
import com.ijpay.wxpay.model.v3.Amount;
import com.ijpay.wxpay.model.v3.Payer;
import com.ijpay.wxpay.model.v3.UnifiedOrderModel;
import com.wilmer.common.core.domain.AjaxResult;
import com.wilmer.common.core.domain.entity.SysUser;
import com.wilmer.common.utils.SecurityUtils;
import com.wilmer.bean.LoginBean;

import com.wilmer.consultants_order.domain.BankConsultantsOrder;
import com.wilmer.consultants_order.mapper.BankConsultantsOrderMapper;
import com.wilmer.pay.config.weChatPayConfig;
import com.wilmer.pay.domain.conPayVo;
import com.wilmer.pay.domain.payVo;
import com.wilmer.pay.service.wechatPayService;
import com.wilmer.pay.utils.OrderUtil;
import com.wilmer.system.service.ISysUserService;
import com.wilmer.utils.OpenIdUtil;
import com.wilmer.videoOrder.domain.VideoOrder;
import com.wilmer.videoOrder.domain.dto.VideoOrderPayDto;
import com.wilmer.videoOrder.mapper.VideoOrderMapper;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static com.wilmer.framework.datasource.DynamicDataSourceContextHolder.log;

/**
 * @author hans
 */
@Service
public class wechatPayServiceImpl implements wechatPayService {

    @Resource
    private weChatPayConfig weChatPayConfig;
    @Resource
    private OpenIdUtil openIdUtil;

    @Resource
    private BankConsultantsOrderMapper bankConsultantsOrderMapper;

    @Resource
    private ISysUserService sysUserService;

    @Resource
    private VideoOrderMapper videoOrderMapper;

    private String getSerialNumber() {
        // 获取证书序列号
        X509Certificate certificate = PayKit.getCertificate(weChatPayConfig.getCertPath());
        if (null != certificate) {
            String serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
            // 提前两天检查证书是否有效
            boolean isValid = PayKit.checkCertificateIsValid(certificate, weChatPayConfig.getMchId(), -2);
            log.info("证书是否可用 {} 证书有效期为 {}", isValid, DateUtil.format(certificate.getNotAfter(), DatePattern.NORM_DATETIME_PATTERN));
            System.out.println("serialNo:" + serialNo);
            return serialNo;
        }
        return null;
    }
    /**
     * 保存平台证书
     * @param associatedData    associated_data
     * @param nonce             nonce
     * @param cipherText        cipherText
     * @param certPath          证书保存路径
     * @return
     */
    private String savePlatformCert(String associatedData, String nonce, String cipherText, String certPath) {
        try {
            AesUtil aesUtil = new AesUtil(weChatPayConfig.getMchKey().getBytes(StandardCharsets.UTF_8));
            // 平台证书密文解密
            // encrypt_certificate 中的  associated_data nonce  ciphertext
            String publicKey = aesUtil.decryptToString(
                    associatedData.getBytes(StandardCharsets.UTF_8),
                    nonce.getBytes(StandardCharsets.UTF_8),
                    cipherText
            );
            // 保存证书
            FileWriter writer = new FileWriter(certPath);
            writer.write(publicKey);
            // 获取平台证书序列号
            X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
            return certificate.getSerialNumber().toString(16).toUpperCase();
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }

    //开启事务将订单写入数据库
    @Transactional
    @Override
    public AjaxResult v3PayCon(conPayVo payVo) throws Exception {
        //生成订单编号
        String orderNumber = OrderUtil.generateOrderNumber();
        BankConsultantsOrder bankConsultantsOrder=new BankConsultantsOrder();

        bankConsultantsOrder.setConId(payVo.getConId());
        bankConsultantsOrder.setOrderNum(orderNumber);
        bankConsultantsOrder.setCreateTime(new Date());
        bankConsultantsOrder.setCreateBy(SecurityUtils.getLoginUser().getUser().getUserName());
        bankConsultantsOrder.setUserId(SecurityUtils.getLoginUser().getUser().getUserId());
        //写入订单
        bankConsultantsOrderMapper.insertBankConsultantsOrder(bankConsultantsOrder);

            //获取用户openId
            LoginBean openId = openIdUtil.getOpenId(payVo.getCode());

            String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);

            UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()

                    // APPID
                    .setAppid(weChatPayConfig.getAppid())
                    // 商户号
                    .setMchid(weChatPayConfig.getMchId())
                    .setDescription("上海威尔默教育科技有限公司")
                    .setOut_trade_no(PayKit.generateStr())
                    .setTime_expire(timeExpire).setAttach("Consultants")
                    .setOut_trade_no(orderNumber)
                    .setNotify_url(weChatPayConfig.getNotifyUrl())
                    .setAmount(new Amount().setTotal(payVo.getPrise()))
                    .setPayer(new Payer().setOpenid(openId.getOpenid()));
            log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
            IJPayHttpResponse response = WxPayApi.v3(
                    RequestMethodEnum.POST,
                    WxDomainEnum.CHINA.toString(),
                    BasePayApiEnum.JS_API_PAY.toString(),
                    weChatPayConfig.getMchId(),
                    getSerialNumber(),
                    null,
                    weChatPayConfig.getCertKeyPath(),
                    JSONUtil.toJsonStr(unifiedOrderModel));
            log.info("统一下单响应 {}", response);
            // 根据证书序列号查询对应的证书来验证签名结果
            boolean verifySignature = WxPayKit.verifySignature(response, weChatPayConfig.getPlatFormPath());
            log.info("verifySignature: {}", verifySignature);
            if (response.getStatus() == HttpStatus.HTTP_OK && verifySignature) {
                String body = response.getBody();
                JSONObject jsonObject = JSONUtil.parseObj(body);
                String prepayId = jsonObject.getStr("prepay_id");
                Map<String, String> map = WxPayKit.jsApiCreateSign(weChatPayConfig.getAppid(), prepayId, weChatPayConfig.getCertKeyPath());
                log.info("唤起支付参数:{}", map);
                return AjaxResult.success("success",JSONUtil.toJsonStr(map)).put("orderNum",orderNumber);
            }
            return AjaxResult.error("唤起失败");
    }



    //开启事务
    @Transactional
    @Override
    public AjaxResult v3Pay(payVo payVo) throws Exception {

        //获取用户openId
        LoginBean openId = openIdUtil.getOpenId(payVo.getCode());

        String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);

        UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()

                // APPID
                .setAppid(weChatPayConfig.getAppid())
                // 商户号
                .setMchid(weChatPayConfig.getMchId())
                .setDescription("威尔默教育科技有限公司")
                .setOut_trade_no(PayKit.generateStr())
                .setTime_expire(timeExpire).setAttach("威尔默教育科技有限公司")
                .setOut_trade_no(OrderUtil.generateOrderNumber())
                .setNotify_url(weChatPayConfig.getNotifyUrl())
                .setAmount(new Amount().setTotal(payVo.getPrise()))
                .setPayer(new Payer().setOpenid(openId.getOpenid()));
        log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
        IJPayHttpResponse response = WxPayApi.v3(
                RequestMethodEnum.POST,
                WxDomainEnum.CHINA.toString(),
                BasePayApiEnum.JS_API_PAY.toString(),
                weChatPayConfig.getMchId(),
                getSerialNumber(),
                null,
                weChatPayConfig.getCertKeyPath(),
                JSONUtil.toJsonStr(unifiedOrderModel));
        log.info("统一下单响应 {}", response);
        // 根据证书序列号查询对应的证书来验证签名结果
        boolean verifySignature = WxPayKit.verifySignature(response, weChatPayConfig.getPlatFormPath());
        log.info("verifySignature: {}", verifySignature);
        if (response.getStatus() == HttpStatus.HTTP_OK && verifySignature) {
            String body = response.getBody();
            JSONObject jsonObject = JSONUtil.parseObj(body);
            String prepayId = jsonObject.getStr("prepay_id");
            Map<String, String> map = WxPayKit.jsApiCreateSign(weChatPayConfig.getAppid(), prepayId, weChatPayConfig.getCertKeyPath());
            log.info("唤起支付参数:{}", map);
            return AjaxResult.success("success",JSONUtil.toJsonStr(map));
        }
        return AjaxResult.error("唤起失败");
    }
    @Transactional
    @SneakyThrows
    @Override
    public AjaxResult videoPay(VideoOrderPayDto dto)  {
        //获取用户openId
        Long userId = SecurityUtils.getUserId();
        SysUser sysUser = sysUserService.selectUserById(userId);
        if(sysUser == null || StringUtils.isEmpty(sysUser.getOpenId())){
            return AjaxResult.error("用户信息获取失败");
        }
        // 获取订单
        VideoOrder videoOrder = videoOrderMapper.selectVideoOrderByOrderNum(dto.getOrderNum());
        if(videoOrder == null){
            return AjaxResult.error("订单信息获取失败");
        }
        String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);

        UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()

                // APPID
                .setAppid(weChatPayConfig.getAppid())
                // 商户号
                .setMchid(weChatPayConfig.getMchId())
                .setDescription("威尔默教育科技有限公司")
                .setOut_trade_no(PayKit.generateStr())
                .setTime_expire(timeExpire).setAttach("Video")
                .setOut_trade_no(dto.getOrderNum())
                .setNotify_url(weChatPayConfig.getNotifyUrl())
                .setAmount(new Amount().setTotal(videoOrder.getPayAmount().multiply(new BigDecimal(100)).intValue()))
                .setPayer(new Payer().setOpenid(sysUser.getOpenId()));
        log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
        IJPayHttpResponse response = WxPayApi.v3(
                RequestMethodEnum.POST,
                WxDomainEnum.CHINA.toString(),
                BasePayApiEnum.JS_API_PAY.toString(),
                weChatPayConfig.getMchId(),
                getSerialNumber(),
                null,
                weChatPayConfig.getCertKeyPath(),
                JSONUtil.toJsonStr(unifiedOrderModel));
        log.info("统一下单响应 {}", response);
        // 根据证书序列号查询对应的证书来验证签名结果
        boolean verifySignature = WxPayKit.verifySignature(response, weChatPayConfig.getPlatFormPath());
        log.info("verifySignature: {}", verifySignature);
        if (response.getStatus() == HttpStatus.HTTP_OK && verifySignature) {
            String body = response.getBody();
            JSONObject jsonObject = JSONUtil.parseObj(body);
            String prepayId = jsonObject.getStr("prepay_id");
            Map<String, String> map = WxPayKit.jsApiCreateSign(weChatPayConfig.getAppid(), prepayId, weChatPayConfig.getCertKeyPath());
            log.info("唤起支付参数:{}", map);
            return AjaxResult.success("success",JSONUtil.toJsonStr(map));
        }
        return AjaxResult.error("唤起失败");
    }

    @Override
    public void payNotify(HttpServletRequest request, HttpServletResponse response) {
        Map<String, String> map = new HashMap<>(12);
        try {
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String serialNo = request.getHeader("Wechatpay-Serial");
            String signature = request.getHeader("Wechatpay-Signature");

            log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
            String result = HttpKit.readData(request);
            log.info("支付通知密文 {}", result);

            // 需要通过证书序列号查找对应的证书，verifyNotify 中有验证证书的序列号
            String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
                    weChatPayConfig.getMchKey(), weChatPayConfig.getPlatFormPath());

            log.info("支付通知明文 {}", plainText);
            //这里根据业务逻辑对订单进行操作

            JSONObject jsonStr = JSONUtil.parseObj(plainText);

            String attach = (String) jsonStr.get("attach");

            String orderNum = (String) jsonStr.get("out_trade_no");

            if(Objects.equals(attach, "Consultants")){
                bankConsultantsOrderMapper.updateOrder(orderNum);
            }
            //其它订单业务逻辑写下面
            if(Objects.equals(attach, "Video")){
                videoOrderMapper.updateVideoPay(orderNum);
            }



            if (StrUtil.isNotEmpty(plainText)) {
                response.setStatus(200);
                map.put("code", "SUCCESS");
                map.put("message", "SUCCESS");
            } else {
                response.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "签名错误");
            }
            response.setHeader("Content-type", ContentType.JSON.toString());
            response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
            response.flushBuffer();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public String v3Get() {
        // 获取平台证书列表
        try {
            IJPayHttpResponse response = WxPayApi.v3(
                    RequestMethodEnum.GET,
                    WxDomainEnum.CHINA.toString(),
                    OtherApiEnum.GET_CERTIFICATES.toString(),
                    weChatPayConfig.getMchId(),
                    getSerialNumber(),
                    null,
                    weChatPayConfig.getCertPath(),
                    ""
            );
            String serialNumber = response.getHeader("Wechatpay-Serial");
            String body = response.getBody();
            int status = response.getStatus();
            log.info("serialNumber: {}", serialNumber);
            log.info("status: {}", status);
            log.info("body: {}", body);
            int isOk = 200;
            if (status == isOk) {
                JSONObject jsonObject = JSONUtil.parseObj(body);
                JSONArray dataArray = jsonObject.getJSONArray("data");
                // 默认认为只有一个平台证书
                JSONObject encryptObject = dataArray.getJSONObject(0);
                JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
                String associatedData = encryptCertificate.getStr("associated_data");
                String cipherText = encryptCertificate.getStr("ciphertext");
                String nonce = encryptCertificate.getStr("nonce");
                String serialNo = encryptObject.getStr("serial_no");
                final String platSerialNo = savePlatformCert(associatedData, nonce, cipherText, weChatPayConfig.getPlatFormPath());
                log.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
            }
            // 根据证书序列号查询对应的证书来验证签名结果
            boolean verifySignature = WxPayKit.verifySignature(response, weChatPayConfig.getPlatFormPath());
            System.out.println("verifySignature:" + verifySignature);
            return body;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
    }
    }
}
